Con el mapeo objeto relacional declaramos en que tablas y columnas de nuestra base de datos relacional se guarda la información de nuestra entidad.
Las herramientas O/R nos permiten trabajar con objetos, en vez de con tablas y columnas y generan automáticamente el código SQL necesario para leer y actualizar la base de datos. De esta forma no necesitamos acceder directamente a la base de datos con SQL, pero para eso tenemos que definir con precisión como se mapean nuestras clases a nuestras tablas, y eso es lo que se hace en las anotaciones de mapeo JPA.
Las entidades OpenXava son entidades JPA, por lo tanto el mapeo objeto/relacional en OpenXava se hace mediante Java Persistence API (JPA). Este capítulo muestra las técnicas más básicas y algunos casos especiales. Si queremos aprender más sobre JPA podemos consultar la documentación de Hibernate Annotations (la implementación de JPA usada por OpenXava por defecto), o cualquier otro manual de JPA que queramos.
Mapeo de entidad
La anotación @Table especifica la tabla principal para la entidad. Se pueden especificar tablas adicionales usando @SecondaryTable o @SecondaryTables.
Si no se especifica @Table para una entidad se aplicaran los valores por defecto.
Ejemplo:
La anotación @Column se usa para especificar como mapear una propiedad persistente. Si no se especifica @Column se aplican los valores por defecto.
Un ejemplo sencillo:
Si necesitamos definir un mapeo para una clave foranea compuesta hemos de usar @JoinColumns. Esta anotación agrupa anotaciones @JoinColumn para la misma reference.
Cuando se usa la anotación @JoinColumns, tanto el atributo nombre como referencedColumnName tienen que especificarse en cada anotación @JoinColumn.
Ejemplo:
Cuando usamos @OneToMany para una colección el mapeo depende de la referencia usada en la otra parte de la asociación, es decir, normalmente no es necesario hacer nada. Pero si estamos usando @ManyToMany, quizás nos sea útil declarar la tabla de unión (@JoinTable), como sigue:
@ElementCollection
@CollectionTable(name="CASAS")// Usa "join column" por defecto
@AttributeOverrides({
@AttributeOverride(name="calle",
column=@Column(name="CASA_CALLE")),
@AttributeOverride(name="localidad",
column=@Column(name="CASA_LOCALIDAD")),
@AttributeOverride(name="provincia",
column=@Column(name="CASA_PROVINCIA"))})privateCollection<Direccion> casasVacaciones;
Si omitimos @CollectionTable y @AttributeOverrides se aplican los valores por defecto.
Mapeo de referencia incrustada
Una referencia incrustada contiene información que en el modelo relacional se guarda en la misma tabla que la entidad principal. Por ejemplo si tenemos un incrustable Direccion asociado a un Cliente, los datos de la dirección se guardan en la misma tabla que los del cliente. ¿Cómo se expresa eso con JPA? Es muy sencillo, usando la anotación @AttributeOverrides, de esta forma:
Si no usamos @AttributeOverrides se asumen valores por defectos.
Conversión de tipo
La conversión de tipos entre Java y la base de datos relacional es un trabajo de la implementación de JPA (OpenXava usa Hibernate por defecto). Normalmente, la conversión de tipos por defecto es buena para la mayoría de los casos, pero si trabajamos con bases de datos legadas quizás necesitemos algunos de los trucos que aquí se muestran.
Dado que OpenXava usa la facilidad de conversión de tipos de Hibernate podemos aprender más en la documentación de Hibernate.
Conversión de propiedad
Cuando el tipo de una propiedad Java y el tipo de su columna correspondiente en la base de datos no coincide necesitamos escribir un Hibernate Type para poder hacer nuestra conversión de tipo personalizada.
Por ejemplo, si tenemos una propiedad de tipo String [], y queremos almacenar su valor concatenándolo en una sola columna de base de datos de tipo VARCHAR. Entonces tenemos que declarar la conversión para nuestra propiedad de esta manera:
packageorg.openxava.test.types;importjava.io.*;importjava.sql.*;importorg.apache.commons.logging.*;importorg.hibernate.*;importorg.hibernate.usertype.*;importorg.hibernate.engine.spi.*;// A partir de OpenXava 5.3 que usa Hibernate 4.3importorg.openxava.util.*;/**
*
* @author Javier Paniza
*/publicclass RegionesType implements UserType {// 1publicint[] sqlTypes(){returnnewint[]{Types.VARCHAR};}publicClass returnedClass(){returnString[].class;}publicboolean equals(Object obj1, Object obj2)throws HibernateException {return Is.equal(obj1, obj2);}publicint hashCode(Object obj)throws HibernateException {return obj.hashCode();}// El argumento SessionImplementor a partir de OpenXava 5.3 que usa Hibernate 4.3publicObject nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor implementor, Object owner)// 2throws HibernateException, SQLException{Object o = resultSet.getObject(names[0]);if(o == null)returnnewString[0];String dbValue = (String) o;String[] javaValue = newString[dbValue.length()];for(int i = 0; i < javaValue.length; i++){
javaValue[i] = String.valueOf(dbValue.charAt(i));}return javaValue;}// El argumento SessionImplementor a partir de OpenXava 5.3 que usa Hibernate 4.3publicvoid nullSafeSet(PreparedStatement ps, Object value, int index, SessionImplementor implementor)// 3throws HibernateException, SQLException{if(value == null){
ps.setString(index, "");return;}String[] javaValue = (String[]) value;StringBuffer dbValue = newStringBuffer();for(int i = 0; i < javaValue.length; i++){
dbValue.append(javaValue[i]);}
ps.setString(index, dbValue.toString());}publicObject deepCopy(Object obj)throws HibernateException {return obj == null?null:((String[]) obj).clone();}publicboolean isMutable(){returntrue;}publicSerializable disassemble(Object obj)throws HibernateException {return(Serializable) obj;}publicObject assemble(Serializable cached, Object owner)throws HibernateException {return cached;}publicObject replace(Object original, Object target, Object owner)throws HibernateException {return original;}}
El conversor de tipo ha de implementar org.hibernate.usertype.UserType (1). Los métodos principales son nullSafeGet (2) para leer de la base de datos y convertir a Java, y nullSafeSet (3) para escribir el valor Java en la base de datos.
OpenXava tiene conversores de tipo de Hibernate genéricos en el paquete org.openxava.types listos para usar. Uno de ellos es EnumLetterType, que permite mapear propiedades de tipo enum. Por ejemplo, si tenemos una propiedad como esta:
private Distancia distancia;publicenum Distancia { LOCAL, NACIONAL, INTERNACIONAL };
En esta propiedad Java 'LOCAL' es 1, 'NATIONAL' es 2 and 'INTERNATIONAL' es 3 cuando la propiedad se almacena en la base de datos. Pero, ¿qué ocurre, si en la base de datos se almacena una única letra ('L', 'N' or 'I')? En este caso podemos usar EnumLetterType de esta forma:
@Type(type="org.openxava.types.EnumLetterType",
parameters={
@Parameter(name="letters", value="LNI"),
@Parameter(name="enumType", value="org.openxava.test.modelo.Albaran$Distancia")})private Distancia distancia;publicenum Distancia { LOCAL, NACIONAL, INTERNACIONAL }
Al poner 'LNI' como valor para letters, hace corresponder la 'L' con 1, la 'N' con 2 y la 'I' con 3. Vemos como el que se puedan configurar propiedades del conversor de tipos nos permite hacer conversores reutilizables.
Conversión con multiples columnas
Con CompositeUserType podemos hacer que varias columnas de la tabla de base de datos correspondan a una propiedad en Java. Esto es útil, por ejemplo cuando tenemos propiedades cuyo tipo Java son clases definidas por nosotros que tienen a su vez varias propiedades susceptibles de ser almacenadas, y también se usa mucho cuando nos enfrentamos a esquemas de bases de datos legados.
Un ejemplo típico sería usar el conversor genérico Date3Type, que permite almacenar en la base de datos 3 columnas y en Java una propiedad java.util.Date.
DIAENTREGA, MESENTREGA y AÑOENTREGA son las tres columnas que en la base de datos guardan la fecha de entrega. Y aquí Date3Type:
packageorg.openxava.types;importjava.io.*;importjava.sql.*;importorg.hibernate.*;importorg.hibernate.engine.*;// Hasta OpenXava 5.2.ximportorg.hibernate.type.*;// A partir de OpenXava 5.3 que usa Hibernate 4.3importorg.hibernate.usertype.*;importorg.openxava.util.*;/**
* In java a <tt>java.util.Date</tt> and in database 3 columns of
* integer type. <p>
*
* @author Javier Paniza
*/publicclass Date3Type implements CompositeUserType {// 1publicString[] getPropertyNames(){returnnewString[]{"year", "month", "day"};}publicType[] getPropertyTypes(){// return new Type[] { Hibernate.INTEGER, Hibernate.INTEGER, Hibernate.INTEGER }; // Antes OpenXava 5.3/Hibernate 4.3returnnewType[]{ IntegerType.INSTANCE, IntegerType.INSTANCE, IntegerType.INSTANCE};// A partir de OpenXava 5.3/Hibernate 4.3}publicObject getPropertyValue(Object component, int property)throws HibernateException {// 2
java.util.Date date = (java.util.Date) component;switch(property){case0:
return Dates.getYear(date);case1:
return Dates.getMonth(date);case2:
return Dates.getYear(date);}thrownew HibernateException(XavaResources.getString("date3_type_only_3_properties"));}publicvoid setPropertyValue(Object component, int property, Object value)throws HibernateException // 3{
java.util.Date date = (java.util.Date) component;int intValue = value == null?0:((Number) value).intValue();switch(property){case0:
Dates.setYear(date, intValue);case1:
Dates.setMonth(date, intValue);case2:
Dates.setYear(date, intValue);}thrownew HibernateException(XavaResources.getString("date3_type_only_3_properties"));}publicClass returnedClass(){return java.util.Date.class;}publicboolean equals(Object x, Object y)throws HibernateException {if(x==y)returntrue;if(x==null || y==null)returnfalse;return!Dates.isDifferentDay((java.util.Date) x, (java.util.Date) y);}publicint hashCode(Object x)throws HibernateException {return x.hashCode();}publicObject nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)throws HibernateException, SQLException// 4{/* Antes OpenXava 5.3/Hibernate 4.3
Number year = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[0] );
Number month = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[1] );
Number day = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[2] );
*/// A partir de OpenXava 5.3/Hibernate 4.3Number year = (Number) IntegerType.INSTANCE.nullSafeGet( rs, names[0], session, owner);Number month = (Number) IntegerType.INSTANCE.nullSafeGet( rs, names[1], session, owner );Number day = (Number) IntegerType.INSTANCE.nullSafeGet( rs, names[2], session, owner );int iyear = year == null?0:year.intValue();int imonth = month == null?0:month.intValue();int iday = day == null?0:day.intValue();return Dates.create(iday, imonth, iyear);}publicvoid nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)throws HibernateException, SQLException// 5{
java.util.Date d = (java.util.Date) value;/* Antes OpenXava 5.3/Hibernate 4.3
Hibernate.INTEGER.nullSafeSet(st, Dates.getYear(d), index);
Hibernate.INTEGER.nullSafeSet(st, Dates.getMonth(d), index + 1);
Hibernate.INTEGER.nullSafeSet(st, Dates.getDay(d), index + 2);
*/// A partir de OpenXava 5.3/Hibernate 4.3
IntegerType.INSTANCE.nullSafeSet(st, Dates.getYear(d), index, session);
IntegerType.INSTANCE.nullSafeSet(st, Dates.getMonth(d), index + 1, session);
IntegerType.INSTANCE.nullSafeSet(st, Dates.getDay(d), index + 2, session);}publicObject deepCopy(Object value)throws HibernateException {
java.util.Date d = (java.util.Date) value;if(value == null)returnnull;return(java.util.Date) d.clone();}publicboolean isMutable(){returntrue;}publicSerializable disassemble(Object value, SessionImplementor session)throws HibernateException
{return(Serializable) deepCopy(value);}publicObject assemble(Serializable cached, SessionImplementor session, Object owner)throws HibernateException
{return deepCopy(cached);}publicObject replace(Object original, Object target, SessionImplementor session, Object owner)throws HibernateException
{return deepCopy(original);}}
Como se ve el conversor de tipo implementa CompositeUserType (1). Los métodos clave son getPropertyValue (2) y setPropertyValue (3) para coger y poner valores en las propiedades del objeto del tipo compuesto, y nullSafeGet (4) y nullSafeSet (5) para leer y grabar este objeto en la base de datos.
Conversión de referencia
La conversión de referencias no se soporta directamente por Hibernate. Pero en alguna circunstancias extremas puede ser que necesitemos hacer conversión de referencias. En esta sección se explica como hacerlo.
Por ejemplo, puede que tengamos una referencia a permiso de conducir usando dos columnas, PERMISOCONDUCIR_NIVEL y PERMISOCONDUCIR_TIPO, y la columna PERMISOCONDUCIR_TIPO no admita nulos, pero es posible que el objeto puede no tener permiso de conducir, en cuyo caso la columna PERMISOCONDUCIR_TIPO almacena una cadena vacía. Esto no es algo normal si nosotros diseñamos la base de datos usando claves foráneas, pero si la base de datos fue diseñada por un programador RPG, por ejemplo, esto se habrá hecho de esta forma, porque los programadores RPG no están acostumbrados a lidiar con nulos.
Es decir, necesitamos una conversión para PERMISOCONDUCIR_TIPO, para transformar el nulo en una cadena vacía. Esto se puede conseguir con un código como este:
// Aplicamos conversión (nulo en una cadena vacía) a la columna PERMISOCONDUCIR_TIPO// Para hacerlo, creamos permisoConducir_nivel y permisoConducir_tipo// Hacemos JoinColumns no insertable ni modificable, modificamos el método get/setPermisoConducir// y creamos un método conversionPermisoConducir().
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({// 1
@JoinColumn(name="PERMISOCONDUCIR_NIVEL", referencedColumnName="NIVEL",
insertable=false, updatable=false),
@JoinColumn(name="PERMISOCONDUCIR_TIPO", referencedColumnName="TIPO",
insertable=false, updatable=false)})private PermisoConducir permisoConducir;privateInteger permisoConducir_nivel;// 2privateString permisoConducir_tipo;// 2public PermisoConducir getPermisoConducir(){// 3// De esta manera porque la columna tipo de permiso de conducir no admite nulostry{if(permisoConducir != null) permisoConducir.toString();// para forzar la cargareturn permisoConducir;}catch(EntityNotFoundException ex){returnnull;}}publicvoid setPermisoConducir(PermisoConducir permiso){// 4// De esta manera porque la columna tipo de permiso de conducir no admite nulosthis.permisoConducir = permiso;this.permisoConducir_nivel = permiso==null?null:permiso.getNivel();this.permisoConducir_tipo = permiso==null?null:permiso.getTipo();}
@PrePersist @PreUpdate
privatevoid conversionPermisoConducir(){// 5if(this.permisoConducir_tipo == null)this.permisoConducir_tipo = "";}
Lo primero poner @JoinColumns con insertable=false y updatable=false en todas las @JoinColumn (1), de esta manera la referencia es leida de la base de datos, pero no escrita. También tenemos que definir propiedades planas para almacenar la clave foránea de la referencia (2).
Ahora tenemos que escribir un getter, getPermisoConducir() (3), para devolver nulo cuand la referencia no se encuentre, y un setter, setPermisoConducir() (4), para asignar la clave de la referencia a las propiedades planas correspondientes.
Finalmente, hemos de escribir un método de retrollamada, conversionPermisoConducir() (5), para hacer el trabajo de conversión. Este método será automáticamente ejecutado al crear y actualizar.
Este ejemplo enseña como es posible envolver bases de datos legadas simplemente usando un poco de programación y algunos recursos básicos de JPA.
Restricciones de valor único
Desde la v4.9 Openxava permite personalizar los mensajes de las restricciones declaradas en el elemento uniqueConstraints de @Table y @SecondaryTable, así como de @Column(unique=true). Para ello se debe tener en cuenta algunas consideraciones previas respecto a Hibernate (la implementación de JPA usada por defecto en Openxava)
Ejemplo:
Al construir nuestra aplicación (ejecutando build.xml), Hibernate utilizará el dialecto declarado en el persistence.xml de tu proyecto, para generar un DDL que mapeará la estructura de tus clases a tablas de la base de datos. Si, por ejemplo, estás trabajando con una base de datos MySQL, mostrará:
createtable APPLICATIONUSER_INFO (
birthdate datetime,
name varchar(40),
sex varchar(255),
id varchar(32)notnull,primary key(id),unique(name, birthdate, sex))createtable ApplicationUser (
id varchar(32)notnull,
nic varchar(8)unique,primary key(id))altertable APPLICATIONUSER_INFO
addindex FK375C9572BA846971 (id),addconstraint FK375C9572BA846971 foreign key(id)references ApplicationUser (id)
Como se observa Hibernate ha mapeado la estructura de nuestra clase ApplicationUser, incluso ha creado la restricciones unique (name, birthdate, sex) y nic varchar(8) unique pero no ha asignado el nombre (“no_repeat_user_info”) declarado en @UniqueConstraint, ni existe un elemento en @Column que nos permita dar nombre a la restricción unique=true, dejando que el motor de base de datos asigne nombres por defecto.
Al producirse una violación a cualquiera de las restricciones anterioriores, Hibernate administrará el error, lanzado por el motor de base de datos, creando una org.hibernate.exception.ConstraintViolationException o en algunos casos una org.hibernate.exception.GenericJDBCException -como es el de HSQL-. ConstraintViolationException tiene una propiedad constraintName que es asignada por Hibernate después de extraer el nombre de la restricción que tiene la base de datos.
Es el dialecto definido en tu proyecto el encargado de extraer el nombre de la restricción que será asignado al contraintName. Ésto lo hace por medio del método extractConstraintName(SQLException sqle) de la interfaz ViolatedConstraintNameExtracter. Pero sucede que los dialectos proporcionados por Hibernate no siempre realizan adecuadamente la extracción del constraintName. Para nuestro ejemplo, el dialecto MySQL5Dialect -de Hibernate 3.6.10 que usa Openxava- ni implementa la interfaz ViolatedConstraintNameExtracter -Hibernate4 ya lo hace adecuadamente-.
Por lo tanto, si quisiéramos generar un mensaje personalizado para las restricciones anteriores, hacemos:
Para mantener coherencia entre el nombre de la @UniqueConstraint y el nombre de la restricción en la base de datos, la que Hibernate administrará por medio del contraintName de ConstraintViolationException, mapeamos manualmente el nombre de la restricción en la base de datos:
createtable APPLICATIONUSER_INFO (
birthdate datetime,
name varchar(40),
sex varchar(255),
id varchar(32)notnull,primary key(id),uniquekey`not_repeat_user_info`(name, birthdate, sex))
Igual para la restricción de @Column:
createtable ApplicationUser (
id varchar(32)notnull,
nic varchar(8),primary key(id),uniquekey`not_repeat_nic`(nic))
Table of Contents
Mapeo objeto/relacional
Con el mapeo objeto relacional declaramos en que tablas y columnas de nuestra base de datos relacional se guarda la información de nuestra entidad.Las herramientas O/R nos permiten trabajar con objetos, en vez de con tablas y columnas y generan automáticamente el código SQL necesario para leer y actualizar la base de datos. De esta forma no necesitamos acceder directamente a la base de datos con SQL, pero para eso tenemos que definir con precisión como se mapean nuestras clases a nuestras tablas, y eso es lo que se hace en las anotaciones de mapeo JPA.
Las entidades OpenXava son entidades JPA, por lo tanto el mapeo objeto/relacional en OpenXava se hace mediante Java Persistence API (JPA). Este capítulo muestra las técnicas más básicas y algunos casos especiales. Si queremos aprender más sobre JPA podemos consultar la documentación de Hibernate Annotations (la implementación de JPA usada por OpenXava por defecto), o cualquier otro manual de JPA que queramos.
Mapeo de entidad
La anotación @Table especifica la tabla principal para la entidad. Se pueden especificar tablas adicionales usando @SecondaryTable o @SecondaryTables.Si no se especifica @Table para una entidad se aplicaran los valores por defecto.
Ejemplo:
Mapeo propiedad
La anotación @Column se usa para especificar como mapear una propiedad persistente. Si no se especifica @Column se aplican los valores por defecto.Un ejemplo sencillo:
Un ejemplo anotando el getter:
Otros ejemplos:
Mapeo de referencia
La anotación @JoinColumn se usa para especificar el mapeo de una columna para una referencia.Ejemplo:
Si necesitamos definir un mapeo para una clave foranea compuesta hemos de usar @JoinColumns. Esta anotación agrupa anotaciones @JoinColumn para la misma reference.
Cuando se usa la anotación @JoinColumns, tanto el atributo nombre como referencedColumnName tienen que especificarse en cada anotación @JoinColumn.
Ejemplo:
Mapeo de colección
Cuando usamos @OneToMany para una colección el mapeo depende de la referencia usada en la otra parte de la asociación, es decir, normalmente no es necesario hacer nada. Pero si estamos usando @ManyToMany, quizás nos sea útil declarar la tabla de unión (@JoinTable), como sigue:Si omitimos @JoinTable se aplican los valores por defecto.
Cuando usamos @ElementCollection (nuevo en v5.0) para una colección podemos usar @CollectionTable y @AttributeOverrides, como sigue:
Si omitimos @CollectionTable y @AttributeOverrides se aplican los valores por defecto.
Mapeo de referencia incrustada
Una referencia incrustada contiene información que en el modelo relacional se guarda en la misma tabla que la entidad principal. Por ejemplo si tenemos un incrustable Direccion asociado a un Cliente, los datos de la dirección se guardan en la misma tabla que los del cliente. ¿Cómo se expresa eso con JPA? Es muy sencillo, usando la anotación @AttributeOverrides, de esta forma:Si no usamos @AttributeOverrides se asumen valores por defectos.
Conversión de tipo
La conversión de tipos entre Java y la base de datos relacional es un trabajo de la implementación de JPA (OpenXava usa Hibernate por defecto). Normalmente, la conversión de tipos por defecto es buena para la mayoría de los casos, pero si trabajamos con bases de datos legadas quizás necesitemos algunos de los trucos que aquí se muestran.Dado que OpenXava usa la facilidad de conversión de tipos de Hibernate podemos aprender más en la documentación de Hibernate.
Conversión de propiedad
Cuando el tipo de una propiedad Java y el tipo de su columna correspondiente en la base de datos no coincide necesitamos escribir un Hibernate Type para poder hacer nuestra conversión de tipo personalizada.Por ejemplo, si tenemos una propiedad de tipo String [], y queremos almacenar su valor concatenándolo en una sola columna de base de datos de tipo VARCHAR. Entonces tenemos que declarar la conversión para nuestra propiedad de esta manera:
La lógica de conversión en RegionesType es:
El conversor de tipo ha de implementar org.hibernate.usertype.UserType (1). Los métodos principales son nullSafeGet (2) para leer de la base de datos y convertir a Java, y nullSafeSet (3) para escribir el valor Java en la base de datos.
OpenXava tiene conversores de tipo de Hibernate genéricos en el paquete org.openxava.types listos para usar. Uno de ellos es EnumLetterType, que permite mapear propiedades de tipo enum. Por ejemplo, si tenemos una propiedad como esta:
En esta propiedad Java 'LOCAL' es 1, 'NATIONAL' es 2 and 'INTERNATIONAL' es 3 cuando la propiedad se almacena en la base de datos. Pero, ¿qué ocurre, si en la base de datos se almacena una única letra ('L', 'N' or 'I')? En este caso podemos usar EnumLetterType de esta forma:
Al poner 'LNI' como valor para letters, hace corresponder la 'L' con 1, la 'N' con 2 y la 'I' con 3. Vemos como el que se puedan configurar propiedades del conversor de tipos nos permite hacer conversores reutilizables.
Conversión con multiples columnas
Con CompositeUserType podemos hacer que varias columnas de la tabla de base de datos correspondan a una propiedad en Java. Esto es útil, por ejemplo cuando tenemos propiedades cuyo tipo Java son clases definidas por nosotros que tienen a su vez varias propiedades susceptibles de ser almacenadas, y también se usa mucho cuando nos enfrentamos a esquemas de bases de datos legados.Un ejemplo típico sería usar el conversor genérico Date3Type, que permite almacenar en la base de datos 3 columnas y en Java una propiedad java.util.Date.
DIAENTREGA, MESENTREGA y AÑOENTREGA son las tres columnas que en la base de datos guardan la fecha de entrega. Y aquí Date3Type:
Como se ve el conversor de tipo implementa CompositeUserType (1). Los métodos clave son getPropertyValue (2) y setPropertyValue (3) para coger y poner valores en las propiedades del objeto del tipo compuesto, y nullSafeGet (4) y nullSafeSet (5) para leer y grabar este objeto en la base de datos.
Conversión de referencia
La conversión de referencias no se soporta directamente por Hibernate. Pero en alguna circunstancias extremas puede ser que necesitemos hacer conversión de referencias. En esta sección se explica como hacerlo.Por ejemplo, puede que tengamos una referencia a permiso de conducir usando dos columnas, PERMISOCONDUCIR_NIVEL y PERMISOCONDUCIR_TIPO, y la columna PERMISOCONDUCIR_TIPO no admita nulos, pero es posible que el objeto puede no tener permiso de conducir, en cuyo caso la columna PERMISOCONDUCIR_TIPO almacena una cadena vacía. Esto no es algo normal si nosotros diseñamos la base de datos usando claves foráneas, pero si la base de datos fue diseñada por un programador RPG, por ejemplo, esto se habrá hecho de esta forma, porque los programadores RPG no están acostumbrados a lidiar con nulos.
Es decir, necesitamos una conversión para PERMISOCONDUCIR_TIPO, para transformar el nulo en una cadena vacía. Esto se puede conseguir con un código como este:
Lo primero poner @JoinColumns con insertable=false y updatable=false en todas las @JoinColumn (1), de esta manera la referencia es leida de la base de datos, pero no escrita. También tenemos que definir propiedades planas para almacenar la clave foránea de la referencia (2).
Ahora tenemos que escribir un getter, getPermisoConducir() (3), para devolver nulo cuand la referencia no se encuentre, y un setter, setPermisoConducir() (4), para asignar la clave de la referencia a las propiedades planas correspondientes.
Finalmente, hemos de escribir un método de retrollamada, conversionPermisoConducir() (5), para hacer el trabajo de conversión. Este método será automáticamente ejecutado al crear y actualizar.
Este ejemplo enseña como es posible envolver bases de datos legadas simplemente usando un poco de programación y algunos recursos básicos de JPA.
Restricciones de valor único
Desde la v4.9 Openxava permite personalizar los mensajes de las restricciones declaradas en el elemento uniqueConstraints de @Table y @SecondaryTable, así como de @Column(unique=true). Para ello se debe tener en cuenta algunas consideraciones previas respecto a Hibernate (la implementación de JPA usada por defecto en Openxava)Ejemplo:
- Al construir nuestra aplicación (ejecutando build.xml), Hibernate utilizará el dialecto declarado en el persistence.xml de tu proyecto, para generar un DDL que mapeará la estructura de tus clases a tablas de la base de datos. Si, por ejemplo, estás trabajando con una base de datos MySQL, mostrará:
Como se observa Hibernate ha mapeado la estructura de nuestra clase ApplicationUser, incluso ha creado la restricciones unique (name, birthdate, sex) y nic varchar(8) unique pero no ha asignado el nombre (“no_repeat_user_info”) declarado en @UniqueConstraint, ni existe un elemento en @Column que nos permita dar nombre a la restricción unique=true, dejando que el motor de base de datos asigne nombres por defecto.Por lo tanto, si quisiéramos generar un mensaje personalizado para las restricciones anteriores, hacemos:
... <property name="hibernate.dialect"value="dialect.XMySQL5Dialect"/> ...Otros dialectos